package soccerscope.training;

import java.util.Random;

import robocup.component.actions.Action;
import robocup.component.actions.CoachAction;
import robocup.component.geometry.Vektor;
import soccerscope.model.Team;
import soccerscope.util.geom.Point2f;

public class ObjectTrainingModel {

  public enum POSITION_TYPE {
    FIXED,
    RECTANGULAR,
    CIRCULAR
  }

  int                   id               = -1;

  // training-data:
  // 1. position:
  private Point2f       position         = new Point2f();

  private float         x_pos_diff       = 0;

  private float         y_pos_diff       = 0;

  private POSITION_TYPE type             = POSITION_TYPE.FIXED;

  // 2. speed - direction:
  private float         speed_angle      = 0;

  private float         speed_angle_diff = 0;

  // 3. speed - length
  private int           max_speed        = 0;

  private int           min_speed        = 0;

  private Point2f       observer         = null;

  /**
   * Constructor references the object to observe!
   * 
   * @param o -
   *          a ball or player
   */
  public ObjectTrainingModel(
      int id) {

    super();
    this.id = id;
  }

  /**
   * This method retrieves the max-speed value in percent, ranging from 0-100.
   * So, it depends on the maximum speed an object can have (e.g. 1.0 as player
   * or 2.7 as ball)
   * 
   * @return the max-speed
   */
  public int getMax_speed() {

    return this.max_speed;
  }

  /**
   * This method sets the max-speed. It has to be greater or equal to min-speed
   * and between 0 and 100, as it is a percentage value. It may modify the
   * min-speed, so that the values will be consistent!
   * 
   * @param max_speed
   *          the new max-speed [0..100]
   */
  public void setMax_speed(int max_speed) {

    this.max_speed = Math.min(100, max_speed);
    this.max_speed = Math.max(0, max_speed);
    if (this.min_speed > this.max_speed) {
      this.min_speed = this.max_speed;
    }
  }

  /**
   * This method retrieves the min-speed value in percent, ranging from 0-100.
   * So, it depends on the speed an object can have (e.g. 1.0 as player or 2.7
   * as ball)
   * 
   * @return the min-speed
   */
  public int getMin_speed() {

    return this.min_speed;
  }

  /**
   * This method sets the min-speed. It has to be smaller or equal to max-speed
   * and between 0 and 100, as it is a percentage value. It may modify the
   * max-speed, so that the values will be consistent!
   * 
   * @param min_speed
   *          the new min-speed [0..100]
   */
  public void setMin_speed(int min_speed) {

    this.min_speed = Math.min(100, min_speed);
    this.min_speed = Math.max(0, min_speed);
    if (this.max_speed < this.min_speed) {
      this.max_speed = this.min_speed;
    }
  }

  // public SoccerObject getObject() {
  // return object;
  // }
  //
  //
  // public void setObject(SoccerObject object) {
  // this.object = object;
  // }

  /**
   * This method retrieves the position of this object!
   */
  public Point2f getPosition() {

    return this.position;
  }

  /**
   * This method sets a new position to the object
   * 
   * @param position -
   *          the new position
   */
  public void setPosition(Point2f position) {

    this.position = position;
  }

  /**
   * This method retrieves the speed-angle, i.e. the direction, the speed has to
   * be.
   * 
   * @return - the speeds direction
   */
  public float getSpeed_angle() {

    return this.speed_angle;
  }

  /**
   * This method sets the direction, the speed has to be, ranging from
   * {-180,180]. Other values will be normalized.
   * 
   * @param speed_angle -
   *          the new speeds angle
   */
  public void setSpeed_angle(float speed_angle) {

    this.speed_angle = (float) Vektor.normalize(speed_angle);
  }

  /**
   * This method retrieves the speeds angle diffusion, which is a cone with
   * center-angle <speed_angle>. This cone defines the angles, which are also
   * possible for speed. The values range from [0..180]. Note, that a value of
   * 45 will create a 90 degree cone, because the diffusion will be added to
   * both sides!
   * 
   * @return the diffusion of the speed
   */
  public float getSpeed_angle_diff() {

    return this.speed_angle_diff;
  }

  /**
   * This method sets the speeds angle diffusion, which is a cone with
   * center-angle <speed_angle>. This cone defines the angles, which are also
   * possible for speed. The values range from [0..180]. Note, that a value of
   * 45 will create a 90 degree cone, because the diffusion will be added to
   * both sides!
   * 
   * @param the
   *          new diffusion of the speed - which will be changed if values are
   *          not allowed, so that, the diffusion ranges between [0..180]
   */
  public void setSpeed_angle_diff(float speed_angle_diff) {

    this.speed_angle_diff = Math.min(180, speed_angle_diff);
    this.speed_angle_diff = Math.max(0, speed_angle_diff);
  }

  /**
   * This method retrieves the positioning type, i.e. if the position has a
   * fixed value (FIXED), is inside of a rectangular space (RECTANGULAR) or
   * insode a circle (CIRCULAR)
   * 
   * @return - the active position-type
   */
  public POSITION_TYPE getType() {

    return this.type;
  }

  /**
   * This method sets the positioning type, i.e. if the position has a fixed
   * value (FIXED), is inside of a rectangular space (RECTANGULAR) or insode a
   * circle (CIRCULAR)
   * 
   * @param type -
   *          the new type
   */
  public void setType(POSITION_TYPE type) {

    this.type = type;
  }

  /**
   * This method retreives the x-position diffusion, which should be always
   * positive. The real diffusion affects both sides, so that the opened space
   * is twice as large.
   * 
   * @return - the x-diffusion
   */
  public float getX_pos_diff() {

    return this.x_pos_diff;
  }

  /**
   * This method sets a new x-diffusion. This value should be always positive.
   * The created space will be in case of RECTANGULAR positions: [position.x -
   * x_pos_diff, position.x - x_pos_diff] When the positioning-type is Circular,
   * this value is used as the x-radius of a oval shape. Fixed positioning
   * ignores this value.
   * 
   * @param x_pos_diff -
   *          the new x-diffusion position value
   */
  public void setX_pos_diff(float x_pos_diff) {

    this.x_pos_diff = x_pos_diff;
  }

  /**
   * This method retreives the y-position diffusion, which should be always
   * positive. The real diffusion affects both sides, so that the opened space
   * is twice as large.
   * 
   * @return - the y-diffusion
   */
  public float getY_pos_diff() {

    return this.y_pos_diff;
  }

  /**
   * This method sets a new y-diffusion. This value should be always positive.
   * The created space will be in case of RECTANGULAR positions: [position.y -
   * y_pos_diff, position.y - y_pos_diff] When the positioning-type is Circular,
   * this value is used as the y-radius of a oval shape. Fixed positioning
   * ignores this value.
   * 
   * @param y_pos_diff -
   *          the new y-diffusion position value
   */
  public void setY_pos_diff(float y_pos_diff) {

    this.y_pos_diff = y_pos_diff;
  }

  /**
   * This method sets a point, which maps from the mouse- position of the
   * trainingsbutton to the soccer-field.
   * 
   * @param observer -
   *          the point, which is selected on the field, when having the mouse
   *          over the trainingsbutton
   */
  public void setObservePoint(Point2f observer) {

    this.observer = observer;
  }

  /**
   * This method retreives the observer-button!
   * 
   * @return
   */
  public Point2f getObservePoint() {

    return this.observer;
  }

  /**
   * This method retrieves a trainer-action, that will satisfy the state of the
   * specified attributes. Executing it will cause the object to have the
   * desired position, speed, etc.
   * 
   * @param left
   * @return a trainer action
   */
  public CoachAction getTrainerAction(int i,
      Team team) {

    CoachAction ca = null;

    // position and speed are general:
    Point2f pos = this.generatePosition();
    Point2f spd = this.generateSpeed();

    if (this instanceof PlayerTrainingModel) {
      // identify the team-side:
      int num = (i < 11) ? i + 1 : i - 10;
      char side = (i < 11) ? 'l' : 'r';
      if (team == null) {
        return null;
      }

      // check, if the player is active
      PlayerTrainingModel ptm = (PlayerTrainingModel) this;
      if (!ptm.isActive()) {
        ca = new CoachAction("(kill_player " + side + " " + num + ")");
        ca.setType(Action.TYPE.COACH_KILL_PLAYER);
        return ca;
      }

      // determine his body-angle
      float bAngle = this.generateBodyAngle(ptm);

      // check, if the speed should be aligned with body-angle
      if (ptm.getAlignSpeedWithBody()) {
        spd = this.generateSpeed(bAngle);
      }

      // create the coach-action and return the string
      ca = new CoachAction(num, team.name, pos.y, pos.x, bAngle, spd.y, spd.x);
      return ca;
    }
    else {

      // create the coach-action and return the string
      ca = new CoachAction(pos.y, pos.x, spd.y, spd.x);
      return ca;
    }
  }

  /**
   * This method generates a body angle, as specified in the trainingModel of
   * the given player
   * 
   * @param ptm -
   *          the playerTrainingModel
   * @return a body angle
   */
  private float generateBodyAngle(PlayerTrainingModel ptm) {

    Random r = new Random();
    float angle = ptm.getBody_angle();
    float off = r.nextFloat() * ptm.getBody_angle_diff();
    if (r.nextBoolean()) {
      off *= -1;
    }
    angle += off;
    return angle;
  }

  /**
   * This method generates a position out of the object-model. The position
   * depends on the type (FIXED, RECTANGULAR, CIRCLE) and the specified data.
   */
  public Point2f generatePosition() {

    Point2f pos = new Point2f();
    if (this.type == POSITION_TYPE.FIXED) {
      pos.set(this.position);
    }
    else if (this.type == POSITION_TYPE.RECTANGULAR) {
      Random r = new Random();
      float xr = r.nextFloat() * this.x_pos_diff;
      if (r.nextBoolean()) {
        xr *= -1;
      }
      float yr = r.nextFloat() * this.y_pos_diff;
      if (r.nextBoolean()) {
        yr *= -1;
      }
      pos.set(this.position);
      pos.x += yr;
      pos.y += xr;
    }
    else if (this.type == POSITION_TYPE.CIRCULAR) {
      Random r = new Random();
      float angle = r.nextFloat() * 360;
      float xps = (this.x_pos_diff * (float) Math.cos(Math.toRadians(angle)));
      float yps = (this.y_pos_diff * (float) Math.sin(Math.toRadians(angle)));
      Vektor v = new Vektor(Vektor.XY, xps, yps);
      v.setLength(v.getLength() * r.nextFloat());
      pos.set(this.position);
      pos.x += v.y;
      pos.y += v.x;
    }
    return pos;
  }

  /**
   * This method generates a speed from the specified values, including
   * randomnes, if specified.
   * 
   * @return a tuple representing the speed
   */
  public Point2f generateSpeed() {

    Point2f spd = new Point2f();
    float spd_max = (this instanceof PlayerTrainingModel) ? 1.2f : 2.7f;

    Random r = new Random();
    // determine the speed-angle:
    float randAngle = r.nextFloat() * this.speed_angle_diff;
    if (r.nextBoolean()) {
      randAngle *= -1;
    }
    randAngle = this.speed_angle + randAngle;

    // determine speed-length:
    float spdlen = ((float) (this.max_speed - this.min_speed)) * r.nextFloat();
    spdlen += this.min_speed;
    spdlen *= (0.01 * spd_max);
    Vektor v = new Vektor(spdlen, randAngle);
    spd.x = (float) v.y;
    spd.y = (float) v.x;
    return spd;
  }

  /**
   * This method generates a speed from the specified values, including
   * randomnes, if specified. The angle of the speed is defined by body-angle!
   * 
   * @return a tuple representing the speed
   */
  public Point2f generateSpeed(float bAngle) {

    Point2f spd = new Point2f();
    float spd_max = (this instanceof PlayerTrainingModel) ? 1.2f : 2.7f;

    Random r = new Random();

    // determine speed-length:
    float spdlen = ((float) (this.max_speed - this.min_speed)) * r.nextFloat();
    spdlen += this.min_speed;
    spdlen *= (0.01 * spd_max);
    Vektor v = new Vektor(spdlen, bAngle);
    spd.x = (float) v.y;
    spd.y = (float) v.x;
    return spd;
  }

}
